﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace UltimateNavigationFramework.Commands
{
    public class NavigationClick
    {
        #region Constants
        private const string DOUBLE_CLICK = "DoubleClick";
        #endregion

        #region Dependency Properties
        private static readonly DependencyProperty UIElementCommandBehaviorProperty = DependencyProperty.RegisterAttached(
            "UIElementCommandBehavior",
            typeof(NavigateCommandBehaviorBase),
            typeof(NavigationClick),
            null);

        /// <summary>
        /// Command to execute on when the event is fired.
        /// </summary>
        public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
            "Command",
            typeof(ICommand),
            typeof(NavigationClick),
            new PropertyMetadata(OnSetCommandCallback));

        /// <summary>
        /// Command parameter to supply on command execution.
        /// </summary>
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
            "CommandParameter",
            typeof(Object),
            typeof(NavigationClick),
            new PropertyMetadata(OnSetCommandParameterCallback));

        /// <summary>
        /// Event name to bind command to.
        /// </summary>
        public static readonly DependencyProperty EventNameProperty = DependencyProperty.RegisterAttached(
            "EventName",
            typeof(String),
            typeof(NavigationClick),
            null);

        /// <summary>
        /// Url name to bind command to.
        /// </summary>
        public static readonly DependencyProperty UrlProperty = DependencyProperty.RegisterAttached(
            "Url",
            typeof(String),
            typeof(NavigationClick),
            new PropertyMetadata(OnSetUrlCallback));

        /// <summary>
        /// FormatA name to bind command to.
        /// </summary>
        public static readonly DependencyProperty FormatParam1Property = DependencyProperty.RegisterAttached(
            "FormatParam1",
            typeof(String),
            typeof(NavigationClick),
            new PropertyMetadata(OnSetFormatParam1Callback));

        /// <summary>
        /// FormatB name to bind command to.
        /// </summary>
        public static readonly DependencyProperty FormatParam2Property = DependencyProperty.RegisterAttached(
            "FormatParam2",
            typeof(String),
            typeof(NavigationClick),
            new PropertyMetadata(OnSetFormatParam2Callback));

        /// <summary>
        /// FormatC name to bind command to.
        /// </summary>
        public static readonly DependencyProperty FormatParam3Property = DependencyProperty.RegisterAttached(
            "FormatParam3",
            typeof(String),
            typeof(NavigationClick),
            new PropertyMetadata(OnSetFormatParam3Callback));
        #endregion

        #region Getters / Setters
        /// <summary>
        /// Sets the <see cref="ICommand"/> to execute when the event is fired.
        /// </summary>
        /// <param name="control">DependencyObject to attach Command.</param>
        /// <param name="command">Command to attach.</param>
        public static void SetCommand(UIElement control, ICommand command)
        {
            control.SetValue(CommandProperty, command);
        }

        /// <summary>
        /// Retrieves the <see cref="ICommand"/> attached to the <see cref="Control"/>.
        /// </summary>
        /// <param name="control">        
        /// DependencyObject containing the Command dependency property.        
        /// </param>
        /// <returns>The value of the command attached.</returns>
        public static ICommand GetCommand(UIElement control)
        {
            return control.GetValue(CommandProperty) as ICommand;
        }

        /// <summary>
        /// Sets the value for the CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject to attach CommandParameter.</param>
        /// <param name="parameter">Parameter value to attach.</param>
        public static void SetCommandParameter(UIElement control, Object parameter)
        {
            control.SetValue(CommandParameterProperty, parameter);
        }

        /// <summary>
        /// Gets the value in CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject that has the CommandParameter.</param>
        /// <returns>The value of the property.</returns>
        public static Object GetCommandParameter(UIElement control)
        {
            return control.GetValue(CommandParameterProperty);
        }

        /// <summary>
        /// Sets the value for the EventName attached property on the provided <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject to attach the EventName to.</param>
        /// <param name="eventName">The name of the event to attach.</param>
        public static void SetEventName(UIElement control, String eventName)
        {
            control.SetValue(EventNameProperty, eventName);
        }

        /// <summary>
        /// Gets the value in EventName attached property on the provided <see cref="Control"/>.
        /// </summary>
        /// <param name="control">The DependencyObject that has the EventName.</param>
        /// <returns>The value of the property.</returns>
        public static String GetEventName(UIElement control)
        {
            return control.GetValue(EventNameProperty).ToString();
        }

        /// <summary>
        /// Sets the value for the CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject to attach CommandParameter.</param>
        /// <param name="parameter">Parameter value to attach.</param>
        public static void SetUrl(UIElement control, String url)
        {
            control.SetValue(UrlProperty, url);
        }

        /// <summary>
        /// Gets the value in CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject that has the CommandParameter.</param>
        /// <returns>The value of the property.</returns>
        public static String GetUrl(UIElement control)
        {
            return control.GetValue(UrlProperty).ToString();
        }

        /// <summary>
        /// Sets the value for the CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject to attach CommandParameter.</param>
        /// <param name="parameter">Parameter value to attach.</param>
        public static void SetFormatParam1(UIElement control, String url)
        {
            control.SetValue(FormatParam1Property, url);
        }

        /// <summary>
        /// Gets the value in CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject that has the CommandParameter.</param>
        /// <returns>The value of the property.</returns>
        public static String GetFormatParam1(UIElement control)
        {
            return control.GetValue(FormatParam1Property).ToString();
        }

        /// <summary>
        /// Sets the value for the CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject to attach CommandParameter.</param>
        /// <param name="parameter">Parameter value to attach.</param>
        public static void SetFormatParam2(UIElement control, String url)
        {
            control.SetValue(FormatParam2Property, url);
        }

        /// <summary>
        /// Gets the value in CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject that has the CommandParameter.</param>
        /// <returns>The value of the property.</returns>
        public static String GetFormatParam2(UIElement control)
        {
            return control.GetValue(FormatParam2Property).ToString();
        }


        /// <summary>
        /// Sets the value for the CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject to attach CommandParameter.</param>
        /// <param name="parameter">Parameter value to attach.</param>
        public static void SetFormatParam3(UIElement control, String url)
        {
            control.SetValue(FormatParam3Property, url);
        }

        /// <summary>
        /// Gets the value in CommandParameter attached property on the provided        
        /// <see cref="Control"/>.
        /// </summary>
        /// <param name="control">DependencyObject that has the CommandParameter.</param>
        /// <returns>The value of the property.</returns>
        public static String GetFormatParam3(UIElement control)
        {
            return control.GetValue(FormatParam3Property).ToString();
        }
        #endregion

        #region Callbacks
        private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var control = dependencyObject as UIElement;
            if (control != null)
            {
                var behavior = GetOrCreateBehavior(control);

                if (behavior != null)
                    behavior.Command = e.NewValue as ICommand;
            }
        }

        private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var control = dependencyObject as UIElement;
            if (control != null)
            {
                var behavior = GetOrCreateBehavior(control);

                if (behavior != null)
                    behavior.CommandParameter = e.NewValue;
            }
        }

        private static void OnSetUrlCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var control = dependencyObject as UIElement;
            if (control != null)
            {
                var behavior = GetOrCreateBehavior(control);

                if (behavior != null)
                    behavior.NavigateUri = e.NewValue != null ? e.NewValue.ToString() : null;
            }
        }

        private static void OnSetFormatParam1Callback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var control = dependencyObject as UIElement;
            if (control != null)
            {
                var behavior = GetOrCreateBehavior(control);

                if (behavior != null)
                    behavior.FormatA = e.NewValue != null ? e.NewValue.ToString() : null;
            }
        }

        private static void OnSetFormatParam2Callback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var control = dependencyObject as UIElement;
            if (control != null)
            {
                var behavior = GetOrCreateBehavior(control);

                if (behavior != null)
                    behavior.FormatB = e.NewValue != null ? e.NewValue.ToString() : null;
            }
        }

        private static void OnSetFormatParam3Callback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            var control = dependencyObject as UIElement;
            if (control != null)
            {
                var behavior = GetOrCreateBehavior(control);

                if (behavior != null)
                    behavior.FormatC = e.NewValue != null ? e.NewValue.ToString() : null;
            }
        }
        #endregion

        #region Private methods
        private static NavigateCommandBehaviorBase GetOrCreateBehavior(UIElement control)
        {
            var behavior = control.GetValue(UIElementCommandBehaviorProperty) as NavigateCommandBehaviorBase;

            if (behavior == null && control.GetValue(EventNameProperty) != null)
            {
                behavior = CreateCommandBehavior(control, control.GetValue(EventNameProperty).ToString());
                control.SetValue(UIElementCommandBehaviorProperty, behavior);
            }

            return behavior;
        }

        private static NavigateCommandBehaviorBase CreateCommandBehavior(UIElement control, String eventName)
        {
            NavigateCommandBehaviorBase behavior;

            if (eventName == DOUBLE_CLICK)
            {
                // The behavior will control the attaching and detaching of events
                behavior = new DoubleClickCommandBehavior(control);

                System.Windows.Interactivity.Behavior behaviorAttachment = behavior;
                behaviorAttachment.Attach(control);
            }
            else
            {
                var type = control.GetType();

                behavior = new ControlNavigationCommandBehavior(control);

                var info = type.GetEvent(eventName);

                if (info != null)
                {
                    var methodInfo = behavior.GetType().GetMethod("Execute");
                    var handler = Delegate.CreateDelegate(info.EventHandlerType, behavior, methodInfo, true);
                    info.AddEventHandler(control, handler);
                }
                else
                {
                    throw new ArgumentException(String.Format("Target object '{0}' doesn't have the event '{1}'.", type.Name, eventName));
                }
            }

            return behavior;
        }
        #endregion
    }
}
